'''
@author: noako
'''
from GeometryHelper import RayRayIntersection, PointToLineDistance, \
                           Bisector, ToTheRight, ToTheLeft, SupportingLine, \
                           IsOpposingEdge
from heapq import heappush
import LAV

class Event:
    def __eq__(self, other):
        if other == None: return False
        return self.time == other.time 
    def __ne__(self, other):
        if other == None: return True
        return self.time != other.time
    def __lt__(self, other):
        return self.time < other.time
    def __le__(self, other):
        return self.time <= other.time
    def __gt__(self, other):
        return self.time > other.time
    def __ge__(self, other):
        return self.time >= other.time

class EdgeEvent(Event):
    def __new__(cls, *args, **kwargs):
        # use new because if there is no intersection we don't want to return an event
        node_left = args[0]
        node_right = args[1]
        edge_event = object.__new__(cls)
        edge_event.intersection = RayRayIntersection(node_left.bisector, node_right.bisector) 
        
        if edge_event.intersection != None:
            return edge_event
    
    def __init__(self, node_left, node_right):
        self.node_left = node_left
        self.node_right = node_right
        line_supporting_edge = SupportingLine(node_left.right_edge)
        self.time = PointToLineDistance(self.intersection, line_supporting_edge) / node_left.right_edge.speed

    def Handle(self, dcel, event_queue):
        if not self.node_left.active or not self.node_right.active:
            return
        # create intersection vertex and edges to it
        v = dcel.CreateVertex(self.intersection)
        dcel.CreateEdge(v, self.node_right.vertex)
        dcel.CreateEdge(v, self.node_left.vertex)
        
        # deactivate the nodes which originated the event
        self.node_left.active = False
        self.node_right.active = False
        
        if self.node_right.right == self.node_left.left:
            # three nodes left in this LAV
            self.node_right.right.active = False
            dcel.CreateEdge(v, self.node_right.right.vertex)
            return
        
        # add one new node and connect it to neighboring nodes
        node = LAV.LAVNode(v)
        LAV.ConnectLAVNodes(self.node_left.left, node, self.node_right.right)
        
        # connect it to neighboring edges
        node.right_edge = self.node_right.right_edge
        node.left_edge = self.node_left.left_edge
        
        # compute new bisector and possible intersections
        node.ComputeBisector(self.time)
        
        edge_events = EdgeEvent(node, node.right), EdgeEvent(node.left, node)
        for event in edge_events:
            if event != None:
                heappush(event_queue, event)
        
        # the new node could be a reflex vertex (unlike in the none-weighted case)
        # so we check for split events with all other possible edges in this LAV
        if LAV.IsReflexVertex(node):
            opposing_edge_left_node = node.right.right
            while opposing_edge_left_node.right != node.left:
                split_event = SplitEvent(node, opposing_edge_left_node)
                if split_event != None:
                    heappush(event_queue, split_event)
                opposing_edge_left_node = opposing_edge_left_node.right

class SplitEvent(Event):
    def __new__(cls, *args, **kwargs):
        # use new because if there is no intersection we don't want to return an event
        node = args[0]
        opposing_edge_left_node = args[1]
        
        split_event = object.__new__(cls)
        
        if not IsOpposingEdge(opposing_edge_left_node.right_edge, node):
            return None
        
        bisector = Bisector(node.right_edge, opposing_edge_left_node.right_edge)
        split_event.intersection = RayRayIntersection(node.bisector, bisector) 
        
        if split_event.intersection == None:
            return None
        
        if (ToTheRight(opposing_edge_left_node.bisector, split_event.intersection) and
            ToTheLeft(opposing_edge_left_node.right.bisector, split_event.intersection)):
            return split_event

    def __init__(self, node, opposing_edge_left_node):
        self.node = node
        self.opposing_edge = opposing_edge_left_node.right_edge
        line_supporting_edge = SupportingLine(self.opposing_edge)
        self.time = PointToLineDistance(self.intersection, line_supporting_edge) / self.opposing_edge.speed
        
    def Handle(self, dcel, event_queue):
        if not self.node.active:
            return
        
        # create intersection vertex and edge to it
        intersection_vertex = dcel.CreateVertex(self.intersection)
        dcel.CreateEdge(self.node.vertex, intersection_vertex)
        self.node.active = False

        if self.node.right.right.right == self.node:
            # three nodes left in this LAV
            dcel.CreateEdge(intersection_vertex, self.node.right.vertex)
            dcel.CreateEdge(intersection_vertex, self.node.right.right.vertex)
            self.node.right.active = False
            self.node.right.right.active = False
            return
        
        # create new nodes
        node1 = LAV.LAVNode(intersection_vertex)
        node2 = LAV.LAVNode(intersection_vertex)
        
        # find the nodes corresponding to the opposing edge in this LAV
        opp_edge_left_node = self.node
        while opp_edge_left_node.right_edge != self.opposing_edge:
            opp_edge_left_node = opp_edge_left_node.right
            assert(opp_edge_left_node != self.node)
        opp_edge_right_node = opp_edge_left_node.right
        
        # connect the new nodes while essentially splitting the LAV
        LAV.ConnectLAVNodes(self.node.left, node1, opp_edge_right_node)
        LAV.ConnectLAVNodes(opp_edge_left_node, node2, self.node.right)
        node1.left_edge = self.node.left_edge
        node2.right_edge = self.node.right_edge
        node1.right_edge = node2.left_edge = self.opposing_edge
        
        # compute the new nodes' bisectors
        node1.ComputeBisector(self.time)
        node2.ComputeBisector(self.time)
        
        # find intersection points if they exist 
        edge_events = (EdgeEvent(node1, node1.right), EdgeEvent(node1.left, node1),
                       EdgeEvent(node2, node2.right), EdgeEvent(node2.left, node2))
        for event in edge_events:
            if event != None:
                heappush(event_queue, event)
